package com.zf.multilang.security;

import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.InputStreamReader;
//总结：CachedBodyHttpServletRequest 的意义
//作用	说明
//✅ 解决流只能读一次的问题	核心价值，让请求体可重复读
//✅ 支持日志、监控、安全检查	可在 Filter 中读取 Body 做审计、XSS 过滤等
//✅ 兼容 Spring MVC	包装后不影响 @RequestBody 解析
//✅ 无侵入性	通过 Filter 自动包装，业务代码无需修改
//它就像一个“带录像功能的快递员”：
//原始快递员（HttpServletRequest）：只能打开包裹看一次，看完就没了。
//CachedBodyHttpServletRequest：第一次收到包裹时，拍个高清视频存下来。以后谁要看，就播放视频，不影响真实包裹的传递。
//常见使用场景：
//        日志记录（打印请求体）
//        接口审计、监控
//        签名验证
//        XSS/SQL 注入过滤（如你提到的 XssFilter）
//        请求重放、调试
//        所以，CachedBodyHttpServletRequest 是实现 XssFilter（尤其是处理 JSON Body）的前提和基础。没有它，你就无法在过滤器中安全地读取和处理请求体。
//如果你不需要多次读取 Body，可以跳过这个缓存逻辑，节省内存。
public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {
/*    问题背景：为什么需要它？
    在 Java Web 开发中，特别是处理 POST 请求时：

    客户端发送的请求体（如 JSON、表单数据）是通过 InputStream 提供的。
            HttpServletRequest.getInputStream() 获取的流 只能读取一次。
    一旦读取完毕，流就关闭了，后续再读会得到空内容。
 解决方案：CachedBodyHttpServletRequest
🔧 它的原理是：
继承 HttpServletRequestWrapper：包装原始的 HttpServletRequest。
在构造时读取一次原始流，并缓存到内存（byte[]）中。
重写 getInputStream() 和 getReader() 方法，每次调用都返回基于缓存数据的新流。
这样，外部代码可以“多次读取”请求体，实际上是从缓存中读取。
    */
    //每次创建 CachedBodyHttpServletRequest 实例时，都会通过构造函数读取原始请求的 InputStream，并将其内容复制到 cachedBody 中。
    //每个实例的 cachedBody 是独立的，不会与其他实例共享。
    private final byte[] cachedBody;//final：一旦初始化后，值不可更改（不可变） 实例变量（instance field），不是类的常量（static field）

    public CachedBodyHttpServletRequest(HttpServletRequest request, byte[] cachedBody) {
        super(request);
        // 1. 构造时读取原始流并缓存
        this.cachedBody = cachedBody;
    }

    @Override
    public ServletInputStream getInputStream() {
        // 2. 每次调用都返回基于缓存的新流
        ByteArrayInputStream bais = new ByteArrayInputStream(cachedBody);
        return new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return bais.available() == 0;
            }

            @Override
            public boolean isReady() {
                return true;
            }

            @Override
            public void setReadListener(ReadListener listener) {
                throw new UnsupportedOperationException();
            }

            @Override
            public int read() {
                return bais.read();
            }
        };
    }

    @Override
    public BufferedReader getReader() {
        // 3. 同样支持 getReader()
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }
    //每次调用 getInputStream() 或 getReader() 时
    //每次调用都会基于 cachedBody 创建一个新的 ByteArrayInputStream。
    //这个流是 从缓存中读取，不会影响原始请求流（因为原始流已经被读取并缓存到 cachedBody 中了）。
    //每个调用者看到的流是独立的，不会相互干扰。
/*    假设你有两个地方需要读取请求体：

    Filter 中：读取 Body 做 XSS 过滤。
    Controller 中：Spring MVC 解析 @RequestBody。
总结
问题	答案
cachedBody 是类的常量吗？	否，是实例变量，每个实例独立。
内存会不会混乱？	不会，每个请求的实例独立，cachedBody 存储的是当前请求的 Body。
缓存请求体会占用多少内存？	占用与请求体大小相同的内存，适用于小数据量场景。
如何保证“从缓存中读取”？	每次调用 getInputStream() 都基于 cachedBody 创建新流，确保独立读取。

大文件上传的优化

如果你的业务涉及大文件上传（如图片、视频），不要使用 CachedBodyHttpServletRequest 缓存整个 Body。应该：
使用流式处理（InputStream 直接读取）。
或在 Filter 中只做头部检查，避免缓存 Body。
内存监控

在高并发场景下，缓存所有请求的 Body 可能导致内存压力。可以通过以下方式优化：
限制 Body 大小（如 spring.servlet.multipart.max-request-size）。
对特定接口禁用缓存（通过 Filter 判断路径）。

    */
}